package com.katesoft.scale4j.web.model;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.envers.Audited;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Index;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Store;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;

import com.katesoft.scale4j.persistent.model.unified.AbstractPersistentEntity;
import com.katesoft.scale4j.web.model.embedded.Account;
import com.katesoft.scale4j.web.model.embedded.Person;
import com.katesoft.scale4j.web.model.embedded.UserPreferences;

/**
 * Classical implementation of User entity.
 * 
 * @author kate2007
 */
@Entity
@Indexed
@Audited
@Table(name = "t_user")
@Access(AccessType.FIELD)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@org.hibernate.annotations.Entity(dynamicUpdate = true)
@NamedQueries(value = { @NamedQuery(
                                    name = User.QUERY_FIND_BY_LOGIN,
                                    query = "select u from User u where u." + User.PROP_LOGIN
                                             + " = :username") })
public class User extends AbstractPersistentEntity implements UserDetails, CredentialsContainer {
   private static final long serialVersionUID = -1267623548176393570L;
   // queries
   public static final String QUERY_FIND_BY_LOGIN = "user.find_by_login";
   // properties
   public static final String PROP_LOGIN = "login";
   public static final String PROP_EMAIL = "email";
   // ***********************************************************************
   // ************************ persistent fields ****************************
   // ***********************************************************************
   @Column(name = "login", unique = true, nullable = false, length = 100)
   @Field(index = Index.TOKENIZED, store = Store.NO)
   private String login;
   @Column(name = "password", nullable = false, length = 255)
   private String password;
   @Column(name = "e_mail", unique = true, length = 255)
   @Field(index = Index.TOKENIZED, store = Store.NO)
   private String email;
   @Column(name = "open_id", length = 100)
   private String openId;
   @Embedded
   @IndexedEmbedded
   private Person person;
   @Embedded
   private Account account;
   @Embedded
   private UserPreferences userPreferences;
   @ManyToMany(fetch = FetchType.LAZY)
   @JoinTable(
              name = "t_user_roles",
              joinColumns = @JoinColumn(name = "user_id"),
              inverseJoinColumns = @JoinColumn(name = "role_id"))
   private Set<Role> roles = new HashSet<Role>();

   // ***********************************************************************
   // ************************* (getters+setters) ***************************
   // ***********************************************************************

   public Person getPerson() {
      if (person == null) {
         person = new Person();
      }
      return person;
   }

   public Account getAccount() {
      if (account == null) {
         account = new Account();
      }
      return account;
   }

   public String getLogin() {
      return login;
   }

   public void setLogin(final String login) {
      this.login = login;
   }

   @Override
   public String getPassword() {
      return password;
   }

   public void setPassword(final String password) {
      this.password = password;
   }

   public String getEmail() {
      return email;
   }

   public void setEmail(final String email) {
      this.email = email;
   }

   public final String getOpenId() {
      return openId;
   }

   public final void setOpenId(final String openId) {
      this.openId = openId;
   }

   public Set<Role> getRoles() {
      if (roles == null) {
         roles = new HashSet<Role>();
      }
      return Collections.checkedSet(roles, Role.class);
   }

   public void setRoles(final Set<Role> roles) {
      this.roles = roles;
   }

   public UserPreferences getUserPreferences() {
      if (userPreferences == null) {
         userPreferences = new UserPreferences();
      }
      return userPreferences;
   }

   public void setUserPreferences(final UserPreferences userPreferences) {
      this.userPreferences = userPreferences;
   }

   // ================ spring security =========================

   @Override
   @Transient
   /**
    * @see org.springframework.security.userdetails.UserDetails#getAuthorities()
    * @return GrantedAuthority[] an array of roles.
    */
   public Collection<GrantedAuthority> getAuthorities() {
      final Set<Role> r = getRoles();
      final Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(r.size());
      for (final Role role : r) {
         authorities.add(new GrantedAuthorityImpl(role.getRole()));
      }
      return authorities;
   }

   @Transient
   @Override
   public String getUsername() {
      return getLogin();
   }

   @Transient
   @Override
   public boolean isAccountNonExpired() {
      return !getAccount().isAccountExpired();
   }

   @Transient
   @Override
   public boolean isAccountNonLocked() {
      return !getAccount().isAccountLocked();
   }

   @Transient
   @Override
   public boolean isCredentialsNonExpired() {
      return !getAccount().isCredentialsExpired();
   }

   @Transient
   @Override
   public boolean isEnabled() {
      return getAccount().isEnabled();
   }

   @Override
   public void eraseCredentials() {
      password = null;
   }
}
